#ifndef CONTROLLER_H
#define CONTROLLER_H
#include <memory>
#include <vector>
#include <map>
#include "ErrorCode.h"
#include "Define.h"
#include "MetaObject.h"
#include "StoreObjectIO.h"
#include "MetaObjectIO.h"
#include "MetaObjectOperation.h"
#include "StoreObjectDiff.h"
#include "StoreObjectMerge.h"

namespace gitstore {

class StoreObject;
class CMetaDataIOBase;
class CObjectIOBase;
struct Node;
struct Branch;
struct Object;
class CObjectOperation;
class CBranchOperation;
class CNodeOperation;
class CDatasetController;

struct dagNode {
    dagNode(const std::string& sversion, std::vector<dagNode*> vchildren = std::vector<dagNode*>{}) {
        version = sversion;
        children.insert(children.begin(), vchildren.begin(), vchildren.end());
        //            parents.insert(parents.begin(), vparents.begin(), vparents.end());
    }
    std::string version;
    std::vector<dagNode*> children;
    //        std::vector<dagNode*> parents;
    bool vis = false;
};

class CObjectController final {
public:
    CObjectController();
    CObjectController(std::shared_ptr<CMetaDataIOBase> io_worker);
    ~CObjectController();

    CObjectController(const CObjectController&) = delete;
    CObjectController(CObjectController&&) = delete;

    int test_template(){return 0;};
    friend CDatasetController;
private:
    std::shared_ptr<CMetaDataIOBase> _io_worker;
    std::unique_ptr<CObjectIOBase> _obj_io_worker;
    std::unique_ptr<CObjectOperation> _ds_op;
    std::unique_ptr<CBranchOperation> _br_op;
    std::unique_ptr<CNodeOperation> _node_op;

private:
    ErrorStatus isBranchExist(const Object &object, const std::string &br_name, bool &b_exist);

    ErrorStatus isVersionInObject(const Object &object, const std::string& version, bool &is_in);

    ErrorStatus isVersionInBranch(const Object &object, const Branch& branch, const std::string& version, bool &is_in);

    ErrorStatus getBranchInfo(const Object &object, const std::string& br_name, Branch &info, int& revision);

    ErrorStatus getNodeInfo(const Object &object, const std::string& version, Node &node, int& revision_node);

    ErrorStatus findCommonAncestor(const Object &object, const Node &node1, const Node& node2, Node& node3);

    ErrorStatus cycleChildren(const std::string& obj_name, const Node& node, std::map<std::string, dagNode*> &graph);

    ErrorStatus findNode(const std::string& find_version, const std::string& cur_version,
                         const std::string& prob_ancestor, std::map<std::string, dagNode*> &graph,
                         bool &result, std::string& ancestor);

    ErrorStatus merge(const Object& object, const std::string& br_name, const std::string& version_merge_in,
                      const std::string& commit_note, const std::string& seperator="");

    template<typename T, typename...Args>
    ErrorStatus merge(const Object& object, const std::string& br_name, const std::string& version_merge_in,
                      const std::string& commit_note, Args&&...args);

    ErrorStatus checkPreviousVersion(const Branch& branch, const std::string& version_previous);

    ErrorStatus getValueByBranch(const Object& object, const std::string& br_name, StoreObject& value,
                                 int& revision_branch);

    ErrorStatus getValueByVersion(const Object& object, const std::string& version, StoreObject& value);

    //public:
    //    ErrorStatus beginTransaction(const std::string& obj_name);

    //    ErrorStatus commitTransaction(const std::string& obj_name);

    //    ErrorStatus rollbackTransaction(const std::string& obj_name);

public:
    ErrorStatus isObjectExist(const std::string& obj_name, bool &b_exist);

    ErrorStatus listObject(std::vector<std::string> &vds);

    ErrorStatus createObject(const std::string& obj_name, const std::string& ds_note,
                             StoreObjectType type, StoreObjectType subtype,
                             const std::string& br_name, const std::string& br_note,
                             const std::string& commit_note, const StoreObject& value,
                             int& revision_obj, int& revision_branch);

    ErrorStatus listObjectBranches(const std::string& obj_name, std::vector<std::string> &vbr);

    ErrorStatus isBranchExist(const std::string& obj_name, const std::string& br_name, bool &b_exist);

    ErrorStatus branchObject(const std::string& obj_name, const std::string& br_refer_name,
                             const std::string& br_new_name, const std::string& br_note,
                             int revision_branch_refer_pre, int& revision_new_branch);

    ErrorStatus getValueByBranch(const std::string& obj_name, const std::string& br_name,
                                 StoreObject& value, int& revision_branch);

    ErrorStatus getValueByVersion(const std::string& obj_name, const std::string& version,
                                  StoreObject& value);

    ErrorStatus getBranchCurNodeInfo(const std::string& obj_name, const std::string& br_name,
                                     Node& node, int& revision_branch);

    ErrorStatus listObjectVersions(const std::string& obj_name, std::vector<std::string> &vbr);

    ErrorStatus getNodeInfo(const std::string& obj_name, const std::string& version, Node& info, int& revision_node);


    //    ErrorStatus putValue(const std::string& obj_name, const std::string& br_name, const StoreObject& value,
    //                         const std::string& commit_note, int revision_branch_previous,
    //                         int& revision_branch_cur);

    ErrorStatus putValue(const std::string& obj_name, const std::string& br_name, const StoreObject& value,
                         const std::string& commit_note, int revision_branch_previous,
                         int& revision_branch_cur);
    //ErrorCode editInfo(const std::string& obj_name, const std::string& br_name, const node_info& info);


    ErrorStatus getObjectInfo(const std::string& obj_name, Object &object, int &revision);

    [[deprecated("deprecated Change to Upper Application.")]]
    ErrorStatus editObjectInfo(const std::string& obj_name, const std::string& note);

    ErrorStatus getBranchInfo(const std::string& obj_name, const std::string& br_name, Branch &info, int& revision);

    [[deprecated("deprecated Change to Upper Application.")]]
    ErrorStatus editBranchInfo(const std::string& obj_name, const std::string& br_name, const std::string &info);

    ErrorStatus resetBranchVersion(const std::string& obj_name, const std::string& br_name,
                                   const std::string& version, int revision_branch_pre, int& revision_new);

    ErrorStatus getBranchVersions(const std::string& obj_name, const std::string& br_name,
                                  std::vector<std::string>& infos, int& revision_branch);

    ErrorStatus isVersionInBranch(const std::string& obj_name, const std::string& br_name,
                                  const std::string& version, bool &is_in);

    ErrorStatus isVersionInObject(const std::string& obj_name, const std::string& version, bool &is_in);


    template<typename T, typename...Args>
    ErrorStatus diffObjectVersion(const std::string& obj_name1, const std::string& version1,
                                  const std::string& obj_name2, const std::string& version2,
                                  T& sres_diff, Args&&...args);

    template<typename T, typename...Args>
    ErrorStatus diffObjectBranch(const std::string& obj_name1, const std::string& br_name1,
                                 const std::string& obj_name2, const std::string& br_name2,
                                 int& revision_branch1, int& revision_branch2,
                                 T& sres_diff, Args&&...args);

//    ErrorStatus diffObjectVersion(const std::string& obj_name1, const std::string& version1,
//                                  const std::string& obj_name2, const std::string& version2,
//                                  const std::string& seperator, std::string& sres_diff);

//    ErrorStatus diffObjectBranch(const std::string& obj_name1, const std::string& br_name1,
//                                 const std::string& obj_name2, const std::string& br_name2,
//                                 const std::string& seperator, std::string& sres_diff,
//                                 int& revision_branch1, int& revision_branch2);

    template<typename T, typename...Args>
    ErrorStatus mergeBranch(const std::string& obj_name, const std::string& br_name,
                            const std::string& refer_br_name, const std::string& commit_note,
                            Args&&...args);

    template<typename T, typename...Args>
    ErrorStatus mergeVersion(const std::string& obj_name, const std::string& br_name,
                             const std::string& version, const std::string& commit_note,
                             Args&&...args);

    ErrorStatus mergeBranch(const std::string& obj_name, const std::string& br_name,
                            const std::string& refer_br_name, const std::string& commit_note,
                            const std::string& seperator="");

    ErrorStatus mergeVersion(const std::string& obj_name, const std::string& br_name,
                             const std::string& version, const std::string& commit_note,
                             const std::string& seperator="");

};


template<typename T, typename...Args>
ErrorStatus CObjectController::diffObjectVersion(const std::string& obj_name1, const std::string& version1,
                                                 const std::string& obj_name2, const std::string& version2,
                                                 T& sres_diff, Args&&...args) {
    Node node1, node2;
    int revision_node1 = NO_REVISION;
    int revision_node2 = NO_REVISION;
    auto res = getNodeInfo(obj_name1, version1, node1, revision_node1);
    if ( Success != res ) {
        return res;
    }
    res = getNodeInfo(obj_name2, version2, node2, revision_node2);
    if ( Success != res ) {
        return res;
    }
    auto type1 = static_cast<StoreObjectType>(node1.type);
    auto type2 = static_cast<StoreObjectType>(node2.type);
    if ( type1 != type2 ) {
        return DifferentTypeCannotDiff;
    }
    if ( type1 == StoreObjectType::Blob ) {
        return TypeCannotDiff;
    }

    StoreObject value1(type1), value2(type1);
    res = _obj_io_worker->getValue(version1, value1);
    if ( Success != res ) {
        return res;
    }
    res = _obj_io_worker->getValue(version2, value2);
    if ( Success != res ) {
        return res;
    }
    return CStoreObjectDiff::diff(value1, value2, sres_diff, args...);
}

template<typename T, typename...Args>
ErrorStatus CObjectController::diffObjectBranch(const std::string& obj_name1, const std::string& br_name1,
                                                const std::string& obj_name2, const std::string& br_name2,
                                                int& revision_branch1, int& revision_branch2, T& sres_diff, Args&&...args) {
    Branch branch1, branch2;
    auto res = getBranchInfo(obj_name1, br_name1, branch1, revision_branch1);
    if ( Success != res ) {
        return res;
    }
    res = getBranchInfo(obj_name2, br_name2, branch2, revision_branch2);
    if ( Success != res ) {
        return res;
    }
    auto type1 = static_cast<StoreObjectType>(branch1.type);
    auto type2 = static_cast<StoreObjectType>(branch2.type);
    if ( type1 != type2 ) {
        return DifferentTypeCannotDiff;
    }
    if ( type1 == StoreObjectType::Blob ) {
        return TypeCannotDiff;
    }

    StoreObject value1(type1), value2(type1);
    res = _obj_io_worker->getValue(branch1.cur_version, value1);
    if ( Success != res ) {
        return res;
    }
    res = _obj_io_worker->getValue(branch2.cur_version, value2);
    if ( Success != res ) {
        return res;
    }
    return CStoreObjectDiff::diff(value1, value2, sres_diff, args...);
}

template<typename T, typename...Args>
ErrorStatus CObjectController::merge(const Object& object, const std::string& br_name, const std::string& version_merge_in,
                                     const std::string& commit_note, Args&&...args) {
    Branch branch;
    int revision_branch = NO_REVISION;
    auto res = getBranchInfo(object, br_name, branch, revision_branch);
    if ( Success != res ) {
        return res;
    }
    auto branch_type = static_cast<StoreObjectType>(branch.type);
    if ( StoreObjectType::Blob == branch_type ) {
        return BlobCannotMerge;
    }
    Node node_br_cur, node_merge_in, node_ancestor;
    int revision_node_cur = NO_REVISION;
    res = getNodeInfo(object, branch.cur_version, node_br_cur, revision_node_cur);
    if ( Success != res ) {
        return res;
    }
    int revision_node_merge = NO_REVISION;
    res = getNodeInfo(object, version_merge_in, node_merge_in, revision_node_merge);
    if ( Success != res ) {
        return res;
    }
    auto merge_in_node_type = static_cast<StoreObjectType>(node_merge_in.type);
    if ( StoreObjectType::Blob == merge_in_node_type ) {
        return BlobCannotMerge;
    }
    res = findCommonAncestor(object, node_br_cur, node_merge_in, node_ancestor);
    if ( Success != res ) {
        return res;
    }
    string version_result;
    StoreObject value_cur(branch_type), value_merge(branch_type), value_ancestor(branch_type), value_result(branch_type);
    res = _obj_io_worker->getValue(branch.cur_version, value_cur);
    if ( Success != res ) {
        return res;
    }
    res = _obj_io_worker->getValue(version_merge_in, value_merge);
    if ( Success != res ) {
        return res;
    }
    res = _obj_io_worker->getValue(node_ancestor.version, value_ancestor);
    if ( Success != res ) {
        return res;
    }

    res = CStoreObjectMerge::merge(value_cur, value_merge, value_ancestor, value_result, args...);
    if ( Success != res ) {
        return res;
    }
    version_result = sha256(value_result.getSerializedValue());
    res = _obj_io_worker->setValue(version_result, value_result);
    if ( Success != res ) {
        return res;
    }

    Node node_result;
    res = _node_op->create(object.name, version_result, commit_note, static_cast<int>(StoreObjectType::String), vector<string>{branch.cur_version, version_merge_in}, node_result);
    if ( Success != res ) {
        return res;
    }
    res = _br_op->setVersion(branch, version_result);
    if ( Success != res ) {
        return res;
    }

    int revision_node_result = NO_REVISION;
    res = _io_worker->createNode(node_result, revision_node_result);
    //    res = _node_op->dump(_io_worker, node_result, NO_REVISION, true);
    if ( Success != res ) {
        return res;
    }
    int revision_new = NO_REVISION;
    res = _io_worker->saveBranch(branch, revision_branch, revision_new);
    //    res = _br_op->dump(_io_worker, branch, revision_branch, false);
    if ( Success != res ) {
        return res;
    }

    return res;
}

template<typename T, typename...Args>
ErrorStatus CObjectController::mergeBranch(const std::string& obj_name, const std::string& br_name,
                                           const std::string& refer_br_name, const std::string& commit_note,
                                           Args&&...args) {
    Object object;
    int revision_object = NO_REVISION;
    auto res = getObjectInfo(obj_name, object, revision_object);
    if ( Success != res ) {
        return res;
    }
    if ( StoreObjectType::String != static_cast<StoreObjectType>(object.type) ) {
        return BlobCannotMerge;
    }
    Branch branch_refer;
    int revision_branch = NO_REVISION;
    res = getBranchInfo(object, refer_br_name, branch_refer, revision_branch);
    if ( Success != res ) {
        return res;
    }
    return merge(object, br_name, branch_refer.cur_version, commit_note, args...);
}

template<typename T, typename...Args>
ErrorStatus CObjectController::mergeVersion(const std::string& obj_name, const std::string& br_name,
                                            const std::string& version, const std::string& commit_note,
                                            Args&&...args) {
    Object object;
    int revision_object = NO_REVISION;
    auto res = getObjectInfo(obj_name, object, revision_object);
    if ( Success != res ) {
        return res;
    }
    if ( StoreObjectType::String != static_cast<StoreObjectType>(object.type) ) {
        return BlobCannotMerge;
    }
    return merge(object, br_name, version, commit_note, args...);
}

}

#endif
